diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-03 10:56:21 +0300 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-03 10:56:21 +0300 |
| commit | 456cf011b36de91c9936994b1fa45703adcd309b (patch) | |
| tree | 8e60daf998f731ac50d100fa490eaecae1168042 /src/pages/og-image/[...slug].png.ts | |
Initial fork of chrismwilliams/astro-theme-cactus theme
Diffstat (limited to 'src/pages/og-image/[...slug].png.ts')
| -rw-r--r-- | src/pages/og-image/[...slug].png.ts | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/src/pages/og-image/[...slug].png.ts b/src/pages/og-image/[...slug].png.ts new file mode 100644 index 0000000..a4982d8 --- /dev/null +++ b/src/pages/og-image/[...slug].png.ts @@ -0,0 +1,90 @@ +import RobotoMonoBold from "@/assets/roboto-mono-700.ttf"; +import RobotoMono from "@/assets/roboto-mono-regular.ttf"; +import { getAllPosts } from "@/data/post"; +import { siteConfig } from "@/site.config"; +import { getFormattedDate } from "@/utils/date"; +import { Resvg } from "@resvg/resvg-js"; +import type { APIContext, InferGetStaticPropsType } from "astro"; +import satori, { type SatoriOptions } from "satori"; +import { html } from "satori-html"; + +const ogOptions: SatoriOptions = { + // debug: true, + fonts: [ + { + data: Buffer.from(RobotoMono), + name: "Roboto Mono", + style: "normal", + weight: 400, + }, + { + data: Buffer.from(RobotoMonoBold), + name: "Roboto Mono", + style: "normal", + weight: 700, + }, + ], + height: 630, + width: 1200, +}; + +const markup = (title: string, pubDate: string) => + html`<div tw="flex flex-col w-full h-full bg-[#1d1f21] text-[#c9cacc]"> + <div tw="flex flex-col flex-1 w-full p-10 justify-center"> + <p tw="text-2xl mb-6">${pubDate}</p> + <h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1> + </div> + <div tw="flex items-center justify-between w-full p-10 border-t border-[#2bbc89] text-xl"> + <div tw="flex items-center"> + <svg height="60" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 272 480"> + <path + d="M181.334 93.333v-40L226.667 80v40l-45.333-26.667ZM136.001 53.333 90.667 26.667v426.666L136.001 480V53.333Z" + fill="#B04304" + ></path> + <path + d="m136.001 119.944 45.333-26.667 45.333 26.667-45.333 26.667-45.333-26.667ZM90.667 26.667 136.001 0l45.333 26.667-45.333 26.666-45.334-26.666ZM181.334 53.277l45.333-26.666L272 53.277l-45.333 26.667-45.333-26.667ZM0 213.277l45.333-26.667 45.334 26.667-45.334 26.667L0 213.277ZM136 239.944l-45.333-26.667v53.333L136 239.944Z" + fill="#FF5D01" + ></path> + <path + d="m136 53.333 45.333-26.666v120L226.667 120V80L272 53.333V160l-90.667 53.333v240L136 480V306.667L45.334 360V240l45.333-26.667v53.334L136 240V53.333Z" + fill="#53C68C" + ></path> + <path d="M45.334 240 0 213.334v120L45.334 360V240Z" fill="#B04304"></path> + </svg> + <p tw="ml-3 font-semibold">${siteConfig.title}</p> + </div> + <p>by ${siteConfig.author}</p> + </div> + </div>`; + +type Props = InferGetStaticPropsType<typeof getStaticPaths>; + +export async function GET(context: APIContext) { + const { pubDate, title } = context.props as Props; + + const postDate = getFormattedDate(pubDate, { + month: "long", + weekday: "long", + }); + const svg = await satori(markup(title, postDate), ogOptions); + const png = new Resvg(svg).render().asPng(); + return new Response(png, { + headers: { + "Cache-Control": "public, max-age=31536000, immutable", + "Content-Type": "image/png", + }, + }); +} + +export async function getStaticPaths() { + const posts = await getAllPosts(); + return posts + .filter(({ data }) => !data.ogImage) + .map((post) => ({ + params: { slug: post.id }, + props: { + pubDate: post.data.updatedDate ?? post.data.publishDate, + title: post.data.title, + }, + })); +} |
